4.6 Consuming Dynamics AX Services
Now that you’ve published your Dynamics AX
services through one of the supported transport technologies, external
service clients can consume them. By consuming published Dynamics AX
services, service clients can leverage business functionality that is
implemented in Dynamics AX. For instance, they can consume the SalesOrderService to manage sales orders in Dynamics AX—to create, read, update, delete, and find them.
How the published Dynamics AX services are
consumed depends on what transport technology you use to publish your
services. We don’t go into the details of configuring asynchronous
adapters for message exchanges in this book; instead, we use WCF Web
service clients to illustrate the overall process and highlight a few
features.
For a more complete description of the available functionality and of asynchronous adapters, refer to the Dynamics AX 2009 Server and Database Administration Guide.
Sample WCF Client for Dynamics AX Services
For the following
discussions, we use a .NET service client. If you want to consume a
Dynamics AX service that has been published as a WCF Web service, you
need to do the following:
- Instantiate and initialize parameters for the call.
- Instantiate the service proxy.
- Set (optional) SOAP headers if necessary.
- Consume the service.
- Evaluate the response and handle exceptions.
The following samples show C# code for each of these steps to consume the find service operation of the Dynamics AX document service CustomerService as it ships with Dynamics AX 2009, with its default configuration. We assume a service reference with the name CustomerService has been added to the project.
To consume the service operation find of the CustomerService document service, you need to instantiate and populate an object of the class QueryCriteria. The following code formulates a query for all Customer entities with an account number that is greater than or equal to 4000.
CustomerService.CriteriaElement[] qe = { new CustomerService.CriteriaElement() };
qe[0].DataSourceName = "CustTable";
qe[0].FieldName = "AccountNum";
qe[0].Operator = CustomerService.Operator.GreaterOrEqual;
qe[0].Value1 = "4000";
CustomerService.QueryCriteria qc = new CustomerService.QueryCriteria();
qc.CriteriaElement = qe;
|
The following code shows how to instantiate a service proxy and consume the service operation find, which executes a query and returns matching entities.
// instantiate proxy
CustomerService.CustomerServiceClient customerService =
new CustomerService.CustomerServiceClient();
CustomerService.AxdCustomer customer;
// see [4] for details about this using directive
using (new OperationContextScope(customerService.InnerChannel))
{
// set optional SOAP headers (see "Overriding Default Values in SOAP Headers")
...
// consume the service operation find()
customer = customerService.find(qc);
}
// error handling – additionally, exceptions should be handled properly
if (null == customer || 0 == ekList.Length)
{
// error handling
}
CustomerService.AxdEntity_CustTable[] custTables = customer.CustTable;
if (null == custTables || 0 == custTables.Length)
{
// error handling
}
// evaluate response
foreach (AxdEntity_CustTable custTable in custTables)
{
custTable.AccountNum = ...
}
|
When
you instantiate the proxy, you should always specify the name of a WCF
client configuration. In addition, if you expect large amounts of data
as a result of the consumption of the service operation find, you might want to use findKeys,
which returns only entity keys for the matching entities instead of the
entire record. You can then, for example, implement pagination logic
that retrieves the matching entities in sizeable chunks.
The other service operations that are supported for document services are consumed in similar ways.
Updating Business Documents
In many scenarios, you need to update data that
already exists in the Dynamics AX data store, for example, to add a
line to a sales order or to update a customer address. Dynamics AX
offers several ways to update your business documents, from full and
partial updates to a document hash.
Full updates
The framework for Dynamics AX services
supports document-centric update operations, that is, updating business
documents (e.g., sales orders). The default behavior for updating
documents is full updates, which includes the following:
- Read the document.
- Apply changes to the document.
- Send the updated document with the update request.
- Handle errors, if any.
The following C# code sample shows the programmatic process of updating a sales order conceptually, using full updates.
// instantiate and populate entityKeys
EntityKey[] entityKeys;
...
// read sales order(s) (including document hash(es)) using entityKeys
AxdSalesOrder salesOrder = service.read(entityKeys);
// process sales order, update data
...
// consume the service to update the record (exception handling not shown)
service.update(entityKeys, salesOrder);
|
In many scenarios, using full updates is
inefficient. Imagine a large sales order with many sales lines—more than
1000 sales lines are not uncommon. According to the full updates
process, you would have to retrieve the entire sales order with all
sales lines, apply your changes to the one sales line you want to
update, and then send the entire sales order with all sales
lines—including all unchanged sales lines—back. This operation can be
costly when you consider the validation and defaulting logic invoked for
each sales line.
Partial updates
Instead of performing full updates, you can apply partial updates. Partial updates use the same service operation as full updates do, that is, update.
Partial updates allow you to send partial documents instead of full
documents. Partial documents contain only changed (added, modified, or
deleted) records and processing instructions for AIF that specify how to
handle each (child) record included in the partial document; the
processing instructions are necessary to avoid ambiguity. Consequently,
the process for updating documents using partial updates contains one
more step in addition to the steps for full updates:
- Read the document (e.g., a sales order).
- Apply changes to the document.
- Explicitly request the mode partial update and add processing instructions.
- Send the updated document with the update request.
- Handle errors, if any.
The following code shows the programmatic process of updating a sales order conceptually, using partial updates.
// instantiate and populate entityKeys
EntityKey[] entityKeys;
...
// read sales order(s) (including document hash(es)) using entityKeys
AxdSalesOrder salesOrder = service.read(entityKeys);
// process sales order, pick data to be updated
...
// update the first sales order and mark it for partial update
AxdEntity_SalesTable[] salesTables = salesOrder.SalesTable;
salesOrder.SalesTable = new AxdEntity_SalesTable[] { salesTables[0] };
salesOrder.SalesTable[0].action = AxdEnum_AxdEntityAction.update;
// delete the first sales line; send only the modified data as part of update()
AxdEntity_SalesLine[] salesLines = salesOrder.SalesTable[0].salesLine;
salesOrder.SalesTable[0].SalesLine = new AxdEntity_SalesLine[] { salesLines[0] };
salesOrder.SalesTable[0].SalesLine[0].action = AxdEnum_AxdEntityAction.delete;
// remove other child data sources (DocuRefHeader, etc.) from salesTable
...
// consume the service to update the record (exception handling not shown)
service.update(entityKeys, salesOrder);
|
In request messages, these processing instructions present themselves in the form of XML action
attributes; this is true for both XML messages sent to asynchronous
adapters as well as for SOAP messages sent to synchronous WCF Web
services. For more details, refer to the Document Services classes
documentation in the Microsoft Dynamics AX 2009 SDK.
Document hashes
Document hashes are hashes that are computed
for a specific document. They include data not only from the root level
data source (e.g., the sales header) but also from all the joined data
sources (e.g., a sales line). In other words, if a table field included
in the business document changes, the document hash changes too.
AIF uses the document hash to implement
optimistic concurrency control (OCC) to compare versions of business
documents. Your code must always read the document before updating it
using the service operation update.
Tip
Caching
a document for a long time on a service client without refreshing it
increases the probability of update requests being rejected because of
colliding updates from other client applications. |
Overriding Default Values in SOAP Headers
To streamline both AIF setup and configuration,
AIF doesn’t require values to be specified in requests for context
information, such as destination endpoint, source endpoint, source
endpoint user, and message identifier. To override the default values
for each of those parameters, you can use code similar to samples in
this section. All the code examples use standard WCF APIs to set the
respective SOAP headers.
Message identifier
By default, WCF generates and uses a message
identifier. Alternatively, you can explicitly specify the message
identifier to be used for sending a request message by explicitly
setting the value for the SOAP header used to exchange message
identifiers. The client application can then use this message identifier
to correlate received response messages with the original requests, for
example.
// generate guid
Guid guid = new Guid();
// use guid as message identifier
OperationContext.Current.OutgoingMessageHeaders.MessageId =
new System.Xml.UniqueId(guid);
// store guid for later use ...
|
Destination endpoint (target company)
By default, AIF uses the default company that
the admninistrator has configured for the user sending the request. If
the request needs to be executed in the context of another company, you
can can specify that company explicitly.
// This assumes that a local endpoint with the name "Contoso" has been configured
// in Dynamics AX and is associated with a company that exists in Dynamics AX
String targetCompany = "Contoso";
// Execute the request in the context of "targetCompany"
OperationContext.Current.OutgoingMessageHeaders.Add(
MessageHeader.CreateHeader(
"DestinationEndpoint",
"http://schemas.microsoft.com/dynamics/2008/01/services",
targetCompany
)
);
|
Source endpoint and source endpoint user
By default, AIF uses the default endpoint as
the default value for the source endpoint and the submitting user as the
default value for the source endpoint user. If the request needs to be
executed in the context of another source endpoint and source endpoint
user, both can be specified explicitly using a standard SOAP header.
// We assume that a source endpoint user "submittingUser" exists in the domain
// "ContosoDomain"; we also assume that the user credentials used to authenticate
// a user from "NorthwindTraders" have been mapped onto an identity within the
// domain "Contoso".
Uri sourceEndpoint = new Uri("urn:NorthwindTraders");
String sourceEndpointUser = "submittingUser\ContosoDomain";
// Create a WCF endpoint address builder.
EndpointAddressBuilder eab = new EndpointAddressBuilder(
new EndpointAddress(
sourceEndpoint
AddressHeader.CreateAddressHeader(
"User",
"http://schemas.microsoft.com/dynamics/2008/01/services",
endpointUser
)
)
);
// Initialize standard SOAP header "From" (see WS Addressing).
OperationContext.Current.OutgoingMessageHeaders.From =
endpointAddress.ToEndpointAddress();
// Execute the request in the context of the given source endpoint and source
// endpoint user ...
|
Sample SOAP message
The following SOAP message represents a
request to create a sales order. This SOAP message overrides the default
values for all the optional headers as described earlier. The
overriding XML elements are shown in bold.
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing"
xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">http://schemas.microsoft.com/dynamics/2008/01/
services/SalesOrderService/create
</a:Action>
<a:From>
<a:Address>urn:RemoteEP</a:Address>
<a:ReferenceParameters>
<SourceEndpointUser xmlns="http://schemas.microsoft.com/dynamics/2008/01/
services">redmond\apurvag</SourceEndpointUser>
</a:ReferenceParameters>
</a:From>
<DestinationEndpoint xmlns="http://schemas.microsoft.com/dynamics/2008/01/
services">LocalEP</DestinationEndpoint>
<a:MessageID>urn:uuid:670bf145-5be2-4c9f-920c-468a4199aa75</a:MessageID>
<a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
</s:Header>
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SalesOrderServiceCreateRequest
xmlns="http://schemas.microsoft.com/dynamics/2008/01/services">
<!-- sales order document -->
</SalesOrderServiceCreateRequest>
</s:Body>
</s:Envelope>
|